package org.jeecg.modules.device.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import com.zhouwr.common.device.vo.InstanceDataStructure;
import com.zhouwr.common.device.vo.InstanceFunctionInputParam;
import com.zhouwr.common.device.vo.InternetDevice.InternetDeviceVo;
import com.zhouwr.common.device.vo.function.FunctionExecuteConfig;
import com.zhouwr.common.device.vo.function.InstanceFunctionVo;
import com.zhouwr.common.device.vo.function.ModelFunctionVo;
import com.zhouwr.common.enums.DeviceInstanceState;
import com.zhouwr.common.network.CommonConnectContext;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.alarm.service.IAlarmConfigService;
import org.jeecg.modules.alarm.service.IAlarmRecordService;
import org.jeecg.modules.device.entity.*;
import org.jeecg.modules.device.enums.DeviceType;
import org.jeecg.modules.device.enums.FunctionType;
import org.jeecg.modules.device.listener.event.InstanceOfflineEvent;
import org.jeecg.modules.device.mapper.DeviceInstanceMapper;
import org.jeecg.modules.device.mapper.DeviceModelMapper;
import org.jeecg.modules.device.quartzJob.InvokeFunctionJob;
import org.jeecg.modules.device.service.*;
import org.jeecg.modules.device.vo.InstanceParamStructure;
import org.jeecg.modules.device.vo.label.ViewDataVo;
import org.jeecg.modules.network.network.NetworkConnectStore;
import org.jeecg.modules.scene.entity.Scene;
import org.jeecg.modules.scene.service.impl.SceneServiceImpl;
import org.jeecg.modules.scene.vo.DeployConfig;
import org.quartz.*;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @Description: 设备实例
 * @Author: jeecg-boot
 * @Date: 2020-04-11
 * @Version: V1.0
 */
@Slf4j
@Service
public class DeviceInstanceServiceImpl extends ServiceImpl<DeviceInstanceMapper, DeviceInstance> implements IDeviceInstanceService {

    @Autowired
    private DeviceInstanceMapper instanceMapper;
    @Autowired
    private DeviceModelMapper modelMapper;
    @Autowired
    private IDeviceInstanceParamService instanceParamService;
    @Autowired
    private IDeviceFunctionService functionService;
    @Autowired
    private IDeviceFunctionParamService functionParamService;
    @Autowired
    private IDeviceDataService dataService;
    @Autowired
    private IDataReportService reportService;
    @Autowired
    private IAlarmRecordService alarmRecordService;
    @Autowired
    private IAlarmConfigService alarmConfigService;
    @Autowired
    private SceneServiceImpl sceneService;
    @Autowired
    private Scheduler scheduler;
    @Autowired
    private ApplicationEventPublisher publisher;

    @Override
    public List<DeviceInstance> listInstanceDeviceByModelType(String modelType) {
        return instanceMapper.getInstanceByModelType(modelType);
    }

    @Override
    public List<InternetDeviceVo> listInternetDevice() {
        return instanceMapper.selectInternetDevice(null, FunctionType.CONNECT_CHECK.getCode());
    }

    @Override
    public Boolean createTable(String instanceId) {
        return true;
    }

    @Override
    public DeviceInstance getInstanceByAddress(String address) {
        InternetDeviceVo internetParam1 = instanceMapper.selectInternetDevice(null, FunctionType.CONNECT_CHECK.getCode())
                .stream()
                .filter(internetDeviceVo -> internetDeviceVo.getInternetParams()
                            .stream()
                            .peek(internetParam -> {
                                log.info("{} == {} => {}", internetParam.getParamValue(), address, internetParam.getParamValue().equals(address));
                            })
                            .anyMatch(internetParam -> internetParam.getParamValue().equals(address))
                )
                .findFirst()
                .orElseThrow(() -> new RuntimeException("根据TCP地址获取不到设备实例！请将实例配置信息的本地地址设置为：" + address));
        return this.instanceMapper.selectById(internetParam1.getId());
    }

    @Override
    public List<DeviceData> getDeviceDatasByModelId(String modelId) {
        return dataService.lambdaQuery().eq(DeviceData::getDeviceModelBy, modelId).list();
    }

    @Override
    public List<DeviceData> getDeviceDatasByInstanceId(String instanceId) {
        DeviceInstance instance = this.getById(instanceId);
        return this.getDeviceDatasByModelId(instance.getModelBy());
    }

    @Override
    public DeviceInstanceState getNetworkState(String instanceId) {
        final DeviceInstance parentGateway = getParentGateway(instanceId);
        final boolean b = instanceParamService.lambdaQuery().eq(DeviceInstanceParam::getInstanceId, parentGateway.getId()).list().stream().anyMatch(instanceParam -> NetworkConnectStore.getNetworkConnectMap().containsKey(instanceParam.getValue().toString()));
        if (b) {
            return DeviceInstanceState.ONLINE;
        } else {
            return DeviceInstanceState.OFFLINE;
        }
    }

    @Override
    public ChannelHandlerContext getNetworkChannel(String instanceId) throws Exception {
        long a = System.currentTimeMillis();
        DeviceInstance instance = getParentGateway(instanceId);
        if (DeviceInstanceState.NOT_ACTIVE.equals(instance.getStatus())) {
            throw new Exception("网关设备实例尚未激活！");
        } else if (DeviceInstanceState.OFFLINE.equals(instance.getStatus())) {
            throw new Exception("网关设备实例离线！");
        }
        final List<CommonConnectContext> connects = NetworkConnectStore.getNetworkConnectMap().entrySet().stream().map(connect -> {
            log.info("getNetworkChannel >>>> {}", connect);
            try {
                Integer count = instanceParamService.lambdaQuery().eq(DeviceInstanceParam::getInstanceId, instanceId).eq(DeviceInstanceParam::getValue, connect.getKey()).count();
                if (count == 1) {
                    return connect.getValue();
                } else if (count > 1) {
                    throw new Exception("网关地址配置重复！");
                } else {
                    throw new Exception("网关地址配置不存在！");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }).collect(Collectors.toList());
        log.info("耗时：{}", System.currentTimeMillis() - a);
        return connects.size() == 1 ? connects.get(0).getChannelHandlerContext() : null;
    }

    @Override
    public DeviceInstance getParentGateway(String instanceId) {
        DeviceInstance instance = this.getById(instanceId);
        DeviceModel model = instanceMapper.getDeviceModelByModelId(instance.getModelBy());
        /* 网关设备 或 直连设备，直接返回 */
        if (model.getType().equals(DeviceType.GATEWAY) || model.getType().equals(DeviceType.DIRECT_TERMINAL)) {
            return instance;
        } else {
            if (StringUtils.isEmpty(instance.getParentBy())) {
                throw new RuntimeException("找不到父级网关设备，请联系管理员！");
            }
            return getParentGateway(instance.getParentBy());
        }
    }

    @Override
    public DeviceInstance getParentInstance(String instanceId) {
        DeviceInstance instance = this.getById(instanceId);
        if (StringUtils.isNotBlank(instance.getParentBy())) {
            instance = instanceMapper.selectById(instance.getParentBy());
        }
        return instance;
    }

    @Override
    public List<DeviceInstance> getParentInstances(String instanceId, List<DeviceInstance> instances) {
        DeviceInstance instance = this.getById(instanceId);
        instances.add(instance);
        if (StringUtils.isNotBlank(instance.getParentBy())) {
            getParentInstances(instance.getParentBy(), instances);
        }
        return instances;
    }

    @Override
    public void setFuncExecConf(InstanceFunctionVo execStructure) {
        FunctionExecuteConfig executeConfig = execStructure.getExecuteConfig();
        functionService.saveOrUpdateExecuteConfig(execStructure.getInstanceId(), execStructure.getId(), executeConfig);
        log.info("更新执行配置：{}，成功", JSON.toJSONString(execStructure.getExecuteConfig()));
    }

    /**
     * 删除执行任务
     *
     * @param execStructure
     */
    @Override
    public void deleteExecJob(InstanceFunctionVo execStructure) {
        try {
            String triggerKey = InvokeFunctionJob.class.getCanonicalName() + "-" + execStructure.getInstanceId() + "-" + execStructure.getCode();
            scheduler.pauseTrigger(TriggerKey.triggerKey(triggerKey));
            scheduler.unscheduleJob(TriggerKey.triggerKey(triggerKey));
            scheduler.deleteJob(JobKey.jobKey(triggerKey));
            // scheduler.clear();
        } catch (Exception e) {
            execStructure.getExecuteConfig().setIsRunning(false);
            this.setFuncExecConf(execStructure);
            e.printStackTrace();
        }
    }

    @Override
    public void addExecJob(InstanceFunctionVo execStructure) throws Exception {
        try {
            if (StringUtils.isBlank(execStructure.getExecuteConfig().getCron())) {
                throw new RuntimeException("功能任务表达式为空！");
            }
            String triggerKey = InvokeFunctionJob.class.getCanonicalName() + "-" + execStructure.getInstanceId() + "-" + execStructure.getCode();
            scheduler.start();
            // 构建job信息
            log.info("构建job信息: {}", JSONObject.toJSONString(execStructure));
            ObjectMapper om = new ObjectMapper();
            log.info("构建job信息2: {}", om.writeValueAsString(execStructure));

            JobDetail jobDetail = JobBuilder.newJob(InvokeFunctionJob.class).withIdentity(triggerKey).usingJobData("executeConfig", om.writeValueAsString(execStructure)).build();

            // 表达式调度构建器(即任务执行的时间)
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(execStructure.getExecuteConfig().getCron());
            // 按新的cronExpression表达式构建一个新的trigger
            CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
            scheduler.scheduleJob(jobDetail, trigger);
            this.setFuncExecConf(execStructure);
        } catch (Exception e) {
            execStructure.getExecuteConfig().setIsRunning(false);
            this.setFuncExecConf(execStructure);
            log.error(e.getMessage());
            throw new RuntimeException(e.getMessage());
        }
    }

    @Override
    public List<DeviceInstance> getInstanceChildren(String id, Boolean withSelf) {
        List<String> ids = Lists.newArrayList(id);
        List<DeviceInstance> instanceChildren = this.getInstanceChildren(ids);
        if (withSelf) {
            instanceChildren.add(this.getById(id));
        }
        return instanceChildren;
    }

    public List<DeviceInstance> getInstanceChildren(List<String> ids) {
        List<DeviceInstance> instances = this.lambdaQuery().in(DeviceInstance::getParentBy, ids).list();
        if (CollectionUtils.isNotEmpty(instances)) {
            List<String> instanceIds = instances.stream().map(DeviceInstance::getId).collect(Collectors.toList());
            instances.addAll(getInstanceChildren(instanceIds));
            return instances;
        } else {
            return Lists.newArrayList();
        }
    }

    @Override
    public List<ModelFunctionVo> getModelExtendParams(String modelId, String instanceId) {

        return modelMapper.selectFunctionsByModelId(modelId).stream().peek(modelFunctionVo -> {
            modelFunctionVo.getInputParams().forEach(inputParam -> {
                if (StringUtils.isNotBlank(instanceId)) {
                    final Object value = instanceParamService.getInstanceParams(instanceId, modelFunctionVo.getId(), inputParam.getId()).getValue();
                    if (value != null) {
                        inputParam.setValue(value.toString());
                    } else {
                        inputParam.setValue(inputParam.getMetadata().getValue());
                    }
                }
            });
        }).collect(Collectors.toList());
    }

    @Override
    public List<InstanceFunctionVo> getInstanceExtendParams(String instanceId) throws Exception {
        return instanceMapper.selectFunctionExecuteByInstanceId(instanceId).stream().peek(modelFunctionVo -> {
            modelFunctionVo.getInputParams().forEach(inputParam -> {
                if (StringUtils.isNotBlank(instanceId)) {
                    final Object value = instanceParamService.getInstanceParams(instanceId, modelFunctionVo.getId(), inputParam.getId()).getValue();
                    if (value != null) {
                        inputParam.setValue(value.toString());
                    } else {
                        inputParam.setValue(inputParam.getMetadata().getValue());
                    }
                }
            });
        }).collect(Collectors.toList());
    }

    /**
     * 功能输入参数，不带数值
     *
     * @param inputParams 功能输入参数
     * @return
     * @throws Exception
     */
    public List<InstanceFunctionInputParam> buildFunctionInputParams(List<DeviceFunctionParam> inputParams) throws Exception {
        return inputParams.stream().map(functionParam -> {
            DeviceData data = dataService.getById(functionParam.getDataId());
            if (data == null) {
                throw new RuntimeException("设备数据节点：" + functionParam.getDataId() + "找不到，请检查配置！！！");
            }
            return new InstanceFunctionInputParam(data.vo(), functionParam.getInputMode());
        }).collect(Collectors.toList());
    }

    /**
     * 功能输入参数，带数值
     *
     * @param inputParams          功能输入参数
     * @param instanceExtendParams 实例扩展参数，对应功能输入参数
     * @return
     * @throws Exception
     */
    public List<InstanceFunctionInputParam> buildFunctionInputParams(List<DeviceFunctionParam> inputParams, List<DeviceInstanceParam> instanceExtendParams) throws Exception {
        return inputParams.stream().map(functionParam -> {
            DeviceData data = dataService.getById(functionParam.getDataId());
            if (data == null) {
                throw new RuntimeException("设备数据节点：" + functionParam.getDataId() + "找不到，请检查配置！！！");
            }
            final List<Object> objects = instanceExtendParams.stream().filter(extentParam -> functionParam.getDirection().getCode().equals(extentParam.getType().getCode()) && functionParam.getFunctionId().equals(extentParam.getFunctionId()) && functionParam.getDataId().equals(extentParam.getDataId())).map(DeviceInstanceParam::getValue).collect(Collectors.toList());

            return new InstanceFunctionInputParam(data.vo(), objects.size() > 0 ? objects.get(0) : null, functionParam.getInputMode());
        }).collect(Collectors.toList());
    }

    /**
     * 构建功能及输入参数结构
     *
     * @param instanceId 设备实例id
     * @return 设备实例扩展参数（功能及输入参数）
     */
    @Override
    public List<InstanceFunctionVo> buildFunctionStructures(String instanceId) {
        // 根据实例id
        return instanceMapper.selectFunctionExecuteByInstanceId(instanceId);
    }

    @Override
    public List<InstanceDataStructure> listInstanceDataHistory(String instanceId, Date begin, Date end, int pageIndex, int pageSize) {

        return instanceMapper.selectInstanceHistoryData(instanceId, begin, end, pageIndex, pageSize);
    }

    @Override
    public List<InstanceDataStructure> listInstanceDataHistory(String instanceId, Date begin, Date now) {
        return instanceMapper.selectInstanceHistoryData(instanceId, begin, now, 0, -1);
    }

    @Override
    public List<InstanceDataStructure> listInstanceDataLatest(String instanceId) {
        return instanceMapper.selectInstanceLatestData(instanceId);
    }

    @Override
    public void batchUpdateInstanceState(String instanceId, DeviceInstanceState status) {
        // 1、更新当前设备状态
        DeviceInstance currentInstance = this.getById(instanceId);
        if (!currentInstance.getStatus().equals(DeviceInstanceState.NOT_ACTIVE)) {
            currentInstance.setStatus(status);
            this.updateById(currentInstance);
        }

        // 2、查询子集设备
        this.lambdaQuery().eq(DeviceInstance::getParentBy, instanceId).ne(DeviceInstance::getStatus, DeviceInstanceState.NOT_ACTIVE).list().forEach(instance -> {
            // 设置状态
            instance.setStatus(status);
            // 设置状态更新时间
            instance.setStatusUpdateTime(new Date());
            this.batchUpdateInstanceState(instance.getId(), instance.getStatus());
        });
    }

    @Override
    @Transactional
    public void updateInstanceParams(InstanceParamStructure structure) {
        final DeviceInstance instance = new DeviceInstance();
        BeanUtils.copyProperties(structure, instance);
        instance.setId(instance.getCode());
        // 更新实例对象
        this.updateById(instance);

        /* 同步下级数据：所属场景、所属方案、所属组织 */
        this.getInstanceChildren(instance.getId(), false).forEach(childInstance -> {
            childInstance.setSceneBy(instance.getSceneBy());
            childInstance.setSceneSchemeBy(instance.getSceneSchemeBy());
            childInstance.setSysOrgCode(instance.getSysOrgCode());
            this.updateById(childInstance);
        });

        // 更新实例参数
        // 先删除实例参数，根据实例id
        boolean b = instanceParamService.updateInstanceParams(structure.getExtendParams());
    }

    @Override
    public void manuallyOffline(String instanceId) {
        log.info("manuallyOffline >>>> {}", instanceId);
        final Map<String, CommonConnectContext> connectMap = NetworkConnectStore.getNetworkConnectMap();
        final Set<String> set = connectMap.keySet();
        for (String key : set) {
            if (connectMap.get(key).getDeviceId().equals(instanceId)) {
                NetworkConnectStore.removeConnect(key);
                return;
            }
        }
        // 不是网关设备时，connectMap里没有实例，需手动触发事件
        publisher.publishEvent(new InstanceOfflineEvent(null, instanceId));
    }

    @Override
    public Scene getScene(String instanceId) {
        return sceneService.getById(this.getSceneId(instanceId));
    }

    @Override
    public String getSceneId(String instanceId) {
        return this.getById(instanceId).getSceneBy();
    }

    @Override
    public List<DeviceInstance> querySameLevelById(String instanceId) {
        DeviceInstance parentInstance = this.getParentInstance(instanceId);
        if (parentInstance == null) {
            throw new RuntimeException();
        }
        return this.lambdaQuery().eq(DeviceInstance::getParentBy, parentInstance.getId()).list();
    }

    @Override
    public List<DeployConfig> listDeployDevice() {
        return instanceMapper.listDeployDevice();
    }

    @Override
    public String getAttributeById(String instanceId, ViewDataVo viewDataVo) {
        String type = viewDataVo.getType();
        String column = oConvertUtils.camelToUnderline(viewDataVo.getKey());
        String dictTable = StringUtils.isNotBlank(viewDataVo.getConvert().getDictTable()) ? oConvertUtils.camelToUnderline(viewDataVo.getConvert().getDictTable()) : "";
        String dictKey = StringUtils.isNotBlank(viewDataVo.getConvert().getDictKey()) ? oConvertUtils.camelToUnderline(viewDataVo.getConvert().getDictKey()) : "";
        String dictValue = StringUtils.isNotBlank(viewDataVo.getConvert().getDictValue()) ? oConvertUtils.camelToUnderline(viewDataVo.getConvert().getDictValue()) : "";
        String dateformat = viewDataVo.getDateformat();
        if ("database".equals(viewDataVo.getType())) {
            if (StringUtils.isBlank(dictTable)) {
                throw new RuntimeException(viewDataVo.getName() + " dictTable is null");
            } else {
                if (StringUtils.isBlank(dictKey)) {
                    throw new RuntimeException(viewDataVo.getName() + " dictKey is null");
                }
                if (StringUtils.isBlank(dictValue)) {
                    throw new RuntimeException(viewDataVo.getName() + " dictValue is null");
                }
            }
        } else if ("dateformat".equals(viewDataVo.getType())) {
            if (StringUtils.isBlank(dateformat)) {
                throw new RuntimeException(viewDataVo.getName() + " dateformat is null");
            }
        } else if ("enum".equals(viewDataVo.getType())) {
            dictTable = null;
        }
        log.info("{} {} {} {} {} {}", instanceId, column, dictTable, dictKey, dictValue, dateformat);
        return instanceMapper.getAttributeById(instanceId, column, dictTable, dictKey, dictValue, dateformat);
    }

    @Override
    public List<InstanceFunctionVo> getInstanceFunctionExecute(String instanceId) {
        return instanceMapper.selectFunctionExecuteByInstanceId(instanceId).stream().filter(functionStructure -> !FunctionType.CONNECT_CHECK.getCode().equals(functionStructure.getType())).map(functionVo -> {
            log.info(JSON.toJSONString(functionVo));
            /* 判断功能信息，根据功能id，如果木有id，直接返回null */
            if (StringUtils.isBlank(functionVo.getId())) {
                return new InstanceFunctionVo();
            }
            functionVo.getInputParams().forEach(inputParam -> {
                if (StringUtils.isNotBlank(instanceId)) {
                    final Object value = instanceParamService.getInstanceParams(instanceId, functionVo.getId(), inputParam.getId()).getValue();
                    if (value != null) {
                        inputParam.setValue(value.toString());
                    } else {
                        inputParam.setValue(inputParam.getMetadata().getValue());
                    }
                }
            });
            return functionVo;
        }).collect(Collectors.toList());
    }

    @Override
    @Transactional
    public boolean deleteInstance(String instanceId) {

        /**
         * 1、删除下级子设备，递归删除
         * 2、删除实例功能参数
         * 3、删除实例上报数据、及告警记录
         * 4、删除实例告警配置
         */
        DeviceInstance instance = instanceMapper.selectById(instanceId);
        // 递归删除子集
        if (StringUtils.isNotBlank(instance.getParentBy())) {
            DeviceInstance parentInstance = instanceMapper.selectById(instance.getParentBy());
            deleteInstance(parentInstance.getId());
        }
        Map<String, Object> removeMap = new HashMap<>() {
            {
                put("instance_id", instanceId);
            }
        };
        // 删除功能参数
        instanceParamService.removeByInstanceId(instanceId);
        // 删除实例上报数据
        reportService.removeByMap(removeMap);
        // 告警记录
        alarmRecordService.removeByMap(removeMap);
        // 实例告警配置
        alarmConfigService.deleteByInstanceId(instanceId);

        return true;
    }
}